/**
 * User: alesj
 * Date: 10.1.2006
 * Time: 17:58:52
 * 
 * (C) Genera Lynx d.o.o.
 */

package com.alesj.blueberry.sar.smpp.impl;

import com.alesj.blueberry.sar.exchange.SMPPServiceHolder;
import com.alesj.blueberry.sar.smpp.PingDeamon;
import ie.omk.smpp.Connection;
import ie.omk.smpp.net.SmscLink;
import ie.omk.smpp.net.TcpLink;
import ie.omk.smpp.version.SMPPVersion;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A service object that communicates with SMSC.
 *
 * @author <a href="mailto:ales.justin@genera-lynx.com">Ales Justin</a>
 * @jmx.mbean
 * @jboss.xmbean
 */
public class SMPPServiceImpl extends AbstractSMPPService implements SMPPServiceImplMBean {

    private Log log = LogFactory.getLog(getClass());

    private SMPPServiceHolder observer;
    private PingDeamon pingDeamon;

    private boolean inReconnection;
    private Thread hook;
    private SmscLink smscLink;

    // config methods

    /**
     * create link
     * @throws Exception
     */
    protected void createSmscLink() throws Exception {
        smscLink = new TcpLink(getHost(), getPort());
    }

    /**
     * close link
     * @throws Exception
     */
    private void closeSmscLink() throws Exception {
        if (smscLink.isConnected()) {
            smscLink.close();
        }
    }

    // ping access methods

    public PingDeamon getPingDeamon() {
        return pingDeamon;
    }

    public void setPingDeamon(PingDeamon pingDeamon) {
        this.pingDeamon = pingDeamon;
    }

    // service lifecycle methods - jmx managed    

    protected void createService() throws Exception {
        // add shutdown hook
        hook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(hook);
        // create smsc link
        log.info("Creating SMSC link [host, port]: " + getHost() + ", " + getPort());
        createSmscLink();
    }

    protected void startService() throws Exception {
        connect();
        if (pingDeamon != null) {
            pingDeamon.runDeamon();
        } else {
            log.info("No ping deamon defined ...");
        }
    }

    protected void stopService() throws Exception {
        if (pingDeamon != null) {
            pingDeamon.stopDeamon();
            pingDeamon = null;
        }
        disconnect(true);
    }

    protected void destroyService() throws Exception {
        hook = null;
        closeSmscLink();
    }

    // service methods

    /**
     * @jmx.managed-attribute
     */
    public SMPPServiceHolder getObserver() {
        return observer;
    }

    /**
     * @jmx.managed-attribute
     */
    public void setObserver(SMPPServiceHolder observer) {
        this.observer = observer;
        observer.setSMPPService(this);
    }

    /**
     * @jmx.managed-operation
     */
    public void connect() throws Exception {
        log.info("Connecting to SMSC [asynch=" + isAsynchronous() + "]");
        setConnection(new Connection(smscLink, isAsynchronous()));
        if (getVersion() != null) {
            log.info("Interface version: " + getVersion());
            getConnection().setInterfaceVersion(SMPPVersion.getVersion(getVersion()));
        }
        // add main observer - output provider
        getConnection().addObserver(getObserver());
        getObserver().setSMPPService(this);
        // bind to SMSC
        log.info("Binding: " + getSystemId() + " [" + getPassword() + "], type = " + getSystemType());
        getConnection().bind(Connection.TRANSCEIVER, getSystemId(), getPassword(), getSystemType());
    }

    public void notifyOfFailure(Throwable t) {
        if (t != null) {
            t.printStackTrace();
        }
        reconnect();
    }

    /**
     * @jmx.managed-operation
     */
    public void reconnect() {
        synchronized (this) {
            if (inReconnection) {
                log.info("Already in reconnection mode.");
                return;
            }
            inReconnection = true;
        }
        int reconnectCount = getReconnectTries();
        while (reconnectCount > 0) {
            try {
                disconnect(false);
                closeSmscLink();
                createSmscLink();
                connect();
                log.info("Reconnection successful.");
                inReconnection = false;
                break;
            } catch (Exception ex) {
                try {
                    log.error("Trying to reconnect failed: " + ex.getMessage() +
                            ", next try in " + getReconnectSleep() + "ms.");
                    Thread.sleep(getReconnectSleep());
                } catch (InterruptedException ignored) {
                    //ignore
                }
            }
            reconnectCount--;
        }
        if (reconnectCount == 0) {
            log.fatal("Failed to reconnect.");
            inReconnection = false;
            //todo notify
            throw new SMPPOutputExhangeException("Reconnection failed.");
        }
    }

    /**
     * @jmx.managed-operation
     */
    public void disconnect() throws Exception {
        disconnect(true);
    }

    private void disconnect(boolean throwException) throws Exception {
        try {
            // unbind and unreference
            log.info("Unbinding connection ...");
            Connection connection = getConnection();
            if (connection != null) {
                // remove observer
                connection.removeObserver(getObserver());
                getObserver().setSMPPService(null);
                // set to null before
                setConnection(null);
                connection.unbind();
                long currentTime = System.currentTimeMillis();
                boolean isTimeout = false;
                while (connection.isBound() && !isTimeout) {
                    isTimeout = System.currentTimeMillis() - currentTime > getUnboundTimeout();
                }
                if (isTimeout) {
                    log.warn("Unable to unbind connection link - timeout, forcing unbind.");
                    connection.force_unbind();
                }
            } else {
                log.info("Connection already unbound - null.");
            }
        } catch (Exception e) {
            log.warn("Unable to unbind connection: " + e.getMessage());
            if (throwException) {
                throw e;
            }
        }
    }

    // if application terminates or crashes
    private class ShutdownHook extends Thread {
        public void run() {
            log.info("Using shutdownhook to close SMPP connection ...");
            hook = null;
            try {
                disconnect(false);
            } catch (Exception ignored) {
            }
        }
    }

}
